/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.rultax.matrix;

import com.rultax.exception.MatrixError;
import java.io.Serializable;

/**
 *
 * @author Scott
 */
public class Matrix implements Cloneable, Serializable {

    public static Matrix createColumnMatrix(final double[] input) {
        final double[][] d = new double[input.length][1];

        for (int row = 0; row < d.length; row++) {
            d[row][0] = input[row];
        }

        return new Matrix(d);
    }

    public static Matrix createRowMatrix(final double[] input) {
        final double[][] d = new double[1][input.length];
        System.arraycopy(input, 0, d[0], 0, input.length);
        return new Matrix(d);
    }
    private double[][] matrix;

    public Matrix(final boolean sourceMatrix[][]) {
        this.matrix = new double[sourceMatrix.length][sourceMatrix[0].length];
        for (int r = 0; r < getRows(); r++) {
            for (int c = 0; c < getCols(); c++) {
                if (sourceMatrix[r][c]) {
                    this.set(r, c, 1);
                } else {
                    this.set(r, c, -1);
                }
            }
        }
    }

    public Matrix(final double sourceMatrix[][]) {
        this.matrix = new double[sourceMatrix.length][sourceMatrix[0].length];
        for (int r = 0; r < getRows(); r++) {
            for (int c = 0; c < getCols(); c++) {
                this.set(r, c, sourceMatrix[r][c]);
            }
        }
    }

    public Matrix(final int rows, final int cols) {
        this.matrix = new double[rows][cols];
    }

    public void add(final int row, final int col, final double value) {
        validate(row, col);
        final double newValue = get(row, col) + value;
        set(row, col, newValue);
    }

    public void clear() {
        for (int r = 0; r < getRows(); r++) {
            for (int c = 0; c < getCols(); c++) {
                set(r, c, 0);
            }
        }
    }

    @Override
    public Matrix clone() {
        return new Matrix(this.matrix);
    }

    public boolean equals(final Matrix matrix) {
        return equals(matrix, 10);
    }

    public boolean equals(final Matrix matrix, int precision) {
        if (precision < 0) {
            throw new MatrixError("Precision can't be a negative number.");
        }

        final double test = Math.pow(10.0, precision);
        if (Double.isInfinite(test) || (test > Long.MAX_VALUE)) {
            throw new MatrixError("Precision of " + precision + " decimal places is not supported.");
        }

        precision = (int) Math.pow(10, precision);

        for (int r = 0; r < getRows(); r++) {
            for (int c = 0; c < getCols(); c++) {
                if ((long) (get(r, c) * precision) != (long) (matrix.get(r, c) * precision)) {
                    return false;
                }
            }
        }

        return true;
    }

    public int fromPackedArray(final Double[] array, int idx) {
        for (int r = 0; r < getRows(); r++) {
            for (int c = 0; c < getCols(); c++) {
                this.matrix[r][c] = array[idx++];
            }
        }

        return idx;
    }

    public double get(final int row, final int col) {
        validate(row, col);
        return this.matrix[row][col];
    }

    public Matrix getCol(final int col) {
        if (col > getCols()) {
            throw new MatrixError("Can't get column #" + col
                    + " because it does not exist.");
        }

        final double newMatrix[][] = new double[getRows()][1];

        for (int row = 0; row < getRows(); row++) {
            newMatrix[row][0] = this.matrix[row][col];
        }

        return new Matrix(newMatrix);
    }

    public int getCols() {
        return this.matrix[0].length;
    }

    public Matrix getRow(final int row) {
        if (row > getRows()) {
            throw new MatrixError("Can't get row #" + row
                    + " because it does not exist.");
        }

        final double newMatrix[][] = new double[1][getCols()];

        for (int col = 0; col < getCols(); col++) {
            newMatrix[0][col] = this.matrix[row][col];
        }

        return new Matrix(newMatrix);
    }

    public int getRows() {
        return this.matrix.length;
    }

    public boolean isVector() {
        if (getRows() == 1) {
            return true;
        } else {
            return getCols() == 1;
        }
    }

    public boolean isZero() {
        for (int row = 0; row < getRows(); row++) {
            for (int col = 0; col < getCols(); col++) {
                if (this.matrix[row][col] != 0) {
                    return false;
                }
            }
        }
        return true;
    }

    public void randomize(final double min, final double max) {
        for (int r = 0; r < getRows(); r++) {
            for (int c = 0; c < getCols(); c++) {
                this.matrix[r][c] = (Math.random() * (max - min)) + min;
            }
        }
    }
    
    public void set(final int row, final int col, final double value){
        validate(row, col);
        if(Double.isInfinite(value) || Double.isNaN(value))
            throw new MatrixError("Trying to assign invalid number to matrix: "
                    + value);
        
        this.matrix[row][col] = value;
    }
    
    public int size(){
        return this.matrix[0].length * this.matrix.length;
    }
    
    public double sum(){
        double result = 0;
        for (int r = 0; r < getRows(); r++) {
            for (int c = 0; c < getCols(); c++) {
                result += this.matrix[r][c];
            }
        }
        
        return result;
    }
    
    public Double[] toPackedArray(){
        final Double result[] = new Double[getRows() * getCols()];
        
        int index = 0;
        for (int r = 0; r < getRows(); r++) {
            for (int c = 0; c < getCols(); c++) {
                result[index++] = this.matrix[r][c];
            }
        }
        
        return result;
    }
    
    private void validate(final int row, final int col){
        if((row >= getRows()) || (row < 0))
            throw new MatrixError("The row: " + row + " is out of range: " +
                    getRows());
        
        if((col >= getCols()) || (col < 0))
            throw new MatrixError("The col: " + col + " is out of range: " +
                    getCols());
    }
}
